package com.lancoo.cvas.arrangecourse.coursescheduling.assignlist;

import com.lancoo.cvas.arrangecourse.common.ClassHourUtil;
import com.lancoo.cvas.arrangecourse.coursescheduling.allocatetimeslotroom.AllocateTimeslotRoom;
import com.lancoo.cvas.arrangecourse.dataprocess.GenerateSchedulingData;
import com.lancoo.cvas.arrangecourse.entities.ClassHour;
import com.lancoo.cvas.arrangecourse.entities.TeachingClassUnit;
import com.lancoo.cvas.arrangecourse.entities.TimeslotRoom;
import com.lancoo.cvas.arrangecourse.rulecheck.RuleService;
import com.lancoo.cvas.pojo.dto.ArrangeTeachingClassDto;
import com.lancoo.cvas.pojo.dto.ArrangeTeachingClassRuleDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * @Description 常规排课类
 * @Author Fortysun
 * @Date 2023/9/4 16:10
 **/
@Slf4j
public abstract class NormalAssign extends GenerateSchedulingData {
    //定义分配教室课时分组的对象
    private SpecifiedTimelotRoom specifiedTimelotRoom;

    /**
     * 接收外面传入的排课规则，以及教室课时分组的分配规则；
     * @param ruleService 排课规则检测类
     * @param allocateTimeslotRoom 教室课时分组分配处理；
     */
    public NormalAssign(RuleService ruleService,
                        AllocateTimeslotRoom allocateTimeslotRoom) {
        //初始化，按教室类型，处理教室课时分组信息列表的对象
        if (specifiedTimelotRoom == null) {
            specifiedTimelotRoom = new SpecifiedTimelotRoom(ruleService, allocateTimeslotRoom);
        }
    }

    /**
     * 将教学班排课单元，转换为ClassHour课表课时对象，并添加到map，方便用来判断排课冲突以及后面的冲突计算
     * @param teachingClassUnit 教学班排课单元
     * @param classHourMap 课表课时map
     */
    protected  abstract ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> GenerateClassHour(TeachingClassUnit teachingClassUnit,
                                               ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap);

    /**
     * 支持设置规则的检测
     * @param ruleService 规则的检测服务，检测规则是否符合要求
     */
    public void setRuleService(RuleService ruleService) {
        specifiedTimelotRoom.setRuleService(ruleService);
    }

    /**
     * 开始指定排课
     * @param taskId 排课计划id
     * @param teachingClassList 教学班列表
     * @param teachingClassUnitList 尽量排课剩下的，未分配的教学班排课单元
     * @param timeslotRoomList 排课使用的教室课时分组信息
     * @param classHourMap 锁定的正常课程，转换过来的排课单元map对象，key为timeslotCode
     * @return
     */
    public LinkedList<TeachingClassUnit> start(Integer taskId,
                                               List<ArrangeTeachingClassDto> teachingClassList,
                                               LinkedList<TeachingClassUnit> teachingClassUnitList,
                                               LinkedList<TimeslotRoom> timeslotRoomList,
                                               ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                               List<ArrangeTeachingClassRuleDto> courseFixList) {
        //获取已经生成的课表对象数量，后面的编号从后面开始
        Integer startPos = ClassHourUtil.getMapElementSize(classHourMap);

        //将教学班列表，以及前面排课，剩余的排课教学班单元信息，一并合在一起，
        // 生成排课教学班排课单元Map，key为教学班所需的教室类型
        Map<Integer, LinkedList<TeachingClassUnit>> teachingClassUnitMap = getTeachingClassUnitMap(taskId,
                teachingClassList, startPos, teachingClassUnitList,courseFixList);

        //按教室类型，将教室课时分组列表数据，进行分组
        Map<Integer, LinkedList<TimeslotRoom>> timeslotRoomMap = getTimeslotRoomMap(timeslotRoomList);

        //为了解决lamda表达式，提示需要用final变量的问题
        //避免下面空指针  new
        Map<Integer, LinkedList<TimeslotRoom>> roomMap = timeslotRoomMap == null ? new HashMap<>() : timeslotRoomMap;

        //定义返回值列表
        LinkedList<TeachingClassUnit> retList = new LinkedList<>();


        /*
        ===========================多个授课方式并发问题===========================================
         */
        if (teachingClassUnitMap != null && !teachingClassUnitMap.isEmpty()){
            LinkedList<TeachingClassUnit> array = new LinkedList<>();
            teachingClassUnitMap.values().forEach(array::addAll);
            Map<Long, LinkedList<TeachingClassUnit>> allTeachingClassUnitMap = array.stream()
                    .collect(Collectors.groupingBy(TeachingClassUnit::getTeachingClassId,Collectors.toCollection(LinkedList::new)));
            LinkedList<TeachingClassUnit> singleHourType = new LinkedList<>();
            LinkedList<TeachingClassUnit> multiHourType = new LinkedList<>();
            for (Long teachingClassId : allTeachingClassUnitMap.keySet()) {
                LinkedList<TeachingClassUnit> teachingClassUnits = allTeachingClassUnitMap.getOrDefault(teachingClassId,new LinkedList<>());
                long count = teachingClassUnits.stream().map(TeachingClassUnit::getTeachingType).distinct().count();
                if (count == 1){
                    singleHourType.addAll(teachingClassUnits);
                }else {
                    multiHourType.addAll(teachingClassUnits);
                }
            }

            //多种授课方式
            teachingClassUnitMap.clear();
            if (!multiHourType.isEmpty()) {
                //按教室类型，将排课数据进行分组
                teachingClassUnitMap = multiHourType
                        .stream()
                        .collect(Collectors.groupingBy(TeachingClassUnit::getRoomType, Collectors.toCollection(LinkedList::new)));
                if (teachingClassUnitMap != null && !teachingClassUnitMap.isEmpty()) {
                    //开始为排课单元分配教室课时分组信息
                    for (Map.Entry<Integer, LinkedList<TeachingClassUnit>> entry : teachingClassUnitMap.entrySet()) {
                        try {
                            //获取当前处理的教室类型，为当前教室类型需求的教学班，分配制定教室类型的教室课时分组信息
                            Integer roomType = entry.getKey();
                            log.info("#NormalAssign.start#--开始为教室类型为：{}的教学班分配教室课时分组信息", roomType);
                            //遍历map中指定教室类型，对应的教学班列表
                            LinkedList<TeachingClassUnit> roomTypeUnitList = entry.getValue();
                            //获取map中，指定教室类型的教室列表信息；
                            LinkedList<TimeslotRoom> roomTypeRoomList = roomMap.getOrDefault(roomType, new LinkedList<>());
                            log.info("#NormalAssign.start#--教室类型为：{}的排课单元数量：{}，待分配的教室课时分组数量：{}",
                                    roomType, (roomTypeUnitList != null ? roomTypeUnitList.size() : 0), (roomTypeRoomList != null ? roomTypeRoomList.size() : 0));
                            //按照指定的教室类型，处理指定教室类型需求的教学班的教室课时分组信息
                            roomTypeUnitList = specifiedTimeslotRoomByRoomType(
                                    roomTypeUnitList,
                                    roomTypeRoomList,
                                    classHourMap,
                                    timeslotRoomList);
                            //如果还剩余未排课的教学班排课单元
                            if (roomTypeUnitList != null && !roomTypeUnitList.isEmpty()) {
                                //添加到返回值列表
                                retList.addAll(roomTypeUnitList);
                            }

                            log.info("#NormalAssign.start#--教室类型为：{}的教学班，分配教室课时分组信息结束,排课单元剩余：{}，教室课时分组剩余：{}",
                                    roomType, (roomTypeUnitList != null ? roomTypeUnitList.size() : 0), (roomTypeRoomList != null ? roomTypeRoomList.size() : 0));
                        } catch (Exception e) {
                            log.error("#NormalAssign.start#--多授课方式排课出现异常：" + e);
                            e.printStackTrace();
                        }
                    }
                }
            }

            //一种授课方式
            teachingClassUnitMap.clear();
            if (!singleHourType.isEmpty()) {
                //按教室类型，将排课数据进行分组
                teachingClassUnitMap = singleHourType
                        .stream()
                        .collect(Collectors.groupingBy(TeachingClassUnit::getRoomType, Collectors.toCollection(LinkedList::new)));
            }
        }
        /*
        =====================================================
         */



        if (teachingClassUnitMap != null && !teachingClassUnitMap.isEmpty()
            //这边如果timeslotRoomMap为空  就不会把没排课的排课单元放进leaveList
//        && timeslotRoomMap != null && !timeslotRoomMap.isEmpty()
        ) {
            //线程池大小
            int size = teachingClassUnitMap.size();
            log.info("#NormalAssign.start#--线程池大小为：{}",size);
            // 创建一个固定大小的线程池,size大小
            ExecutorService executor = Executors.newFixedThreadPool(size);
            // 创建一个CyclicBarrier，设置屏障点为size
            CyclicBarrier barrier = new CyclicBarrier(size);
            if (teachingClassUnitMap != null
                    && !teachingClassUnitMap.isEmpty()) {
                //开始为排课单元分配教室课时分组信息
                for (Map.Entry<Integer, LinkedList<TeachingClassUnit>> entry : teachingClassUnitMap.entrySet()) {
                    executor.submit(() -> {
                        try {
                            //获取当前处理的教室类型，为当前教室类型需求的教学班，分配制定教室类型的教室课时分组信息
                            Integer roomType = entry.getKey();
                            log.info("#NormalAssign.start#--开始为教室类型为：{}的教学班分配教室课时分组信息", roomType);
                            //遍历map中指定教室类型，对应的教学班列表
                            LinkedList<TeachingClassUnit> roomTypeUnitList = entry.getValue();
                            //获取map中，指定教室类型的教室列表信息；
                            LinkedList<TimeslotRoom> roomTypeRoomList = roomMap.getOrDefault(roomType,new LinkedList<>());
                            log.info("#NormalAssign.start#--教室类型为：{}的排课单元数量：{}，待分配的教室课时分组数量：{}",
                                    roomType, (roomTypeUnitList != null ? roomTypeUnitList.size() : 0), (roomTypeRoomList != null ? roomTypeRoomList.size() : 0));
                            //按照指定的教室类型，处理指定教室类型需求的教学班的教室课时分组信息
                            roomTypeUnitList = specifiedTimeslotRoomByRoomType(
                                    roomTypeUnitList,
                                    roomTypeRoomList,
                                    classHourMap,
                                    timeslotRoomList);
                            //如果还剩余未排课的教学班排课单元
                            if (roomTypeUnitList != null && !roomTypeUnitList.isEmpty()) {
                                //添加到返回值列表
                                retList.addAll(roomTypeUnitList);
                            }

                            log.info("#NormalAssign.start#--教室类型为：{}的教学班，分配教室课时分组信息结束,排课单元剩余：{}，教室课时分组剩余：{}",
                                    roomType, (roomTypeUnitList != null ? roomTypeUnitList.size() : 0), (roomTypeRoomList != null ? roomTypeRoomList.size() : 0));
                        } catch (Exception e) {
                            log.error("#NormalAssign.start#--executor.submit出现异常：" + e);
                            e.printStackTrace();
                        } finally {
                            try {
                                // 等待所有任务完成
                                barrier.await();
                            } catch (InterruptedException | BrokenBarrierException e) {
                                log.error("#NormalAssign.start#--barrier.await出现异常：" + e);
                                e.printStackTrace();
                            }
                        }
                    });
                }

                // 关闭线程池，此时线程池不再接受新的任务，但会继续处理已提交的任务
                executor.shutdown();
                try {
                    // 阻塞主线程，等待所有任务完成或超时
                    boolean tasksCompleted = executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
                    if (tasksCompleted) {
                        log.error("#NormalScheduling.startScheduling#--All tasks completed");
                    } else {
                        log.error("#NormalScheduling.startScheduling#--Timeout occurred while waiting for tasks to complete");
                    }
                } catch (InterruptedException e) {
                    log.error("#NormalScheduling.startScheduling#--executor.awaitTermination出现异常：" + e);
                    // 处理中断异常
                    e.printStackTrace();
                }
            }
        }


        if (teachingClassUnitMap != null
                && !teachingClassUnitMap.isEmpty()) {
            teachingClassUnitMap.clear();
            teachingClassUnitMap = null;
        }

        if (timeslotRoomMap != null
                && !timeslotRoomMap.isEmpty()) {
            timeslotRoomMap.clear();
            timeslotRoomMap = null;
        }

        return  retList;
    }

    /**
     * 处理指定教室类型的
     * @param teachingClassUnitList 排课教学班单元列表
     * @param timeslotRoomList  所属教室类型的，教室课时分组M列表
     * @param classHourMap 已经排好课的课表信息
     * @param orgTimeslotRoomList 原始的教室课时分组信息列表，应该删除掉已经分配的，所以传入进行处理
     * @return
     */
    private LinkedList<TeachingClassUnit> specifiedTimeslotRoomByRoomType(LinkedList<TeachingClassUnit> teachingClassUnitList,
                                                                          LinkedList<TimeslotRoom> timeslotRoomList,
                                                                          ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                                                          LinkedList<TimeslotRoom> orgTimeslotRoomList) {
        //如果当前分类的教学班列表为空，或者没有数据，那么进行返回
        if (teachingClassUnitList == null
                || teachingClassUnitList.isEmpty()) {
            return null;
        }

        if (timeslotRoomList == null || timeslotRoomList.isEmpty()){
            return teachingClassUnitList;
        }

//        Collections.shuffle(teachingClassUnitList);
//        Collections.sort(teachingClassUnitList);

        //规则多的先排课
        teachingClassUnitList.sort(Comparator.comparing(TeachingClassUnit::getRuleNum).reversed());


        if (timeslotRoomList != null && !timeslotRoomList.isEmpty()) {
            //先对排课单元进行乱序，这样在下面的排序的结果
            //可以实现相对无序，因为相同容量大小的排课单元比较多
            //所以顺序可以变化
            Collections.shuffle(timeslotRoomList);
            //以下将两个列表都按照从小到大排序，可以实现将排课单元安排在合理的位置
            //比如学生人数为60，如果教室课时分组中，教室的容量是60、61、70，可以确保，精确匹配60
            //同时因为每匹配到一个元素，教室课时分组列表，会删除一个元素，所以整个列表会慢慢变短，能够提升查找的效率，
            //如果是从大到小排序，可以一开始学生人数为120，没有教室有这么大容量，那么需要遍历整个列表
            //然后按照教室容量，从低到高排序
            Collections.sort(timeslotRoomList);
        }
        //开始第一轮分配，将教室课时分组列表，按照教室id，进行分类存储为map
        TreeMap<Long, LinkedList<TimeslotRoom>> timeslotRoomMap = timeslotRoomList2MapByRoomId(timeslotRoomList);
        //定义列表遍历的迭代器
        Iterator iterator = teachingClassUnitList.iterator();
        while (iterator.hasNext()) {
            TeachingClassUnit teachingClassUnit = (TeachingClassUnit) iterator.next();

            log.info("#NormalAssign.arranging---specifiedTimeslotRoomByRoomType #--排课单元：{}",
                    teachingClassUnit.getTeachingClassName() + "--" + teachingClassUnit.getCourseName());


            //按教室，对教学班排课单元，进行教室课时分组信息的分配
            teachingClassUnit = specifiedTimeslotRoomByRoomId(teachingClassUnit, timeslotRoomMap, classHourMap);

            //获取分配的map对象；
            LinkedList<TimeslotRoom> specifiedTimeslotRoomList= teachingClassUnit.getTimeslotRoomList();

            //如果已经分配了教室课时分组信息列表
            if (specifiedTimeslotRoomList != null
                    && !specifiedTimeslotRoomList.isEmpty()) {

                //将教学班排课单元，拆分到ClassHour课表课时中，进行排课冲突的判断，以及后面的冲突检测
                classHourMap = GenerateClassHour(teachingClassUnit, classHourMap);
                //已经分配的教学班，直接将其从列表删除
                //剩下未分配的，在第二轮中，进行匹配(冲突匹配，只要连上节次相符，就分配)
                iterator.remove();
                //如果是可删除的教室课时分组，需要从当前列表中删除
                timeslotRoomList  = removeTimeslotRoomList(timeslotRoomList, specifiedTimeslotRoomList);
                //如果是可删除的教室课时分组，需要从原始的列表中删除
                orgTimeslotRoomList  = removeTimeslotRoomList(orgTimeslotRoomList, specifiedTimeslotRoomList);
            }
        }

        if (timeslotRoomMap != null
                && !timeslotRoomMap.isEmpty()) {
            timeslotRoomMap.clear();
            timeslotRoomMap = null;
        }

        return teachingClassUnitList;
    }

    /**
     * 将教室课时信息，按照roomid的形式，存入map
     * 这里使用TreeMap，是为了保证map的有序性
     * 因为之前按照教室容量从低到高，做好了排序了
     * 需要保证这种顺序
     * @param timeslotRoomList 教室课时分组列表
     * @return
     */
    public TreeMap<Long, LinkedList<TimeslotRoom>> timeslotRoomList2MapByRoomId(LinkedList<TimeslotRoom> timeslotRoomList) {

        //定义返回的map类
        TreeMap<Long, LinkedList<TimeslotRoom>> timeslotRoomMap = new TreeMap<>();
        if (timeslotRoomList != null && !timeslotRoomList.isEmpty()) {
            //遍历教室课时分组信息列表
            for (TimeslotRoom timeslotRoom : timeslotRoomList) {
                Long key = timeslotRoom.getRoomId();
                if (timeslotRoomMap.get(key) == null) {
                    LinkedList<TimeslotRoom> mapList = new LinkedList<>();
                    mapList.add(timeslotRoom);
                    timeslotRoomMap.put(key, mapList);
                } else {
                    timeslotRoomMap.get(key).add(timeslotRoom);
                }
            }
        }

        return timeslotRoomMap;
    }

    /**
     * 为教学班分配教室课时，按照单个教室，分配的课时，去进行分配
     * @param teachingClassUnit 教学班排课单元
     * @param timeslotRoomMap 教室课时分组信息map
     * @param classHourMap 课表课时map
     * @return
     */
    public TeachingClassUnit specifiedTimeslotRoomByRoomId(TeachingClassUnit teachingClassUnit,
                                                           TreeMap<Long, LinkedList<TimeslotRoom>> timeslotRoomMap,
                                                           ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap) {

        //遍历教室课时分组的map，map的key是教室id，即一间教室、一间教室的进行处理和分配
        for (Map.Entry<Long, LinkedList<TimeslotRoom>> entry : timeslotRoomMap.entrySet()) {
            //获取教学班指定需要分配的教室
            String specifiedRoomId = teachingClassUnit.getRoomId();
            //如果教学班没有指定排课教室，或者指定了排课的教室
            if (StringUtils.isBlank(specifiedRoomId) ||
                    (StringUtils.isNotBlank(specifiedRoomId) && specifiedRoomId.equals(String.valueOf(entry.getKey())))) {
                //获取指定教室的，教室课时分组信息列表
                LinkedList<TimeslotRoom> roomList = entry.getValue();

                log.info("#NormalAssign.arranging---specifiedTimeslotRoomByRoomId #--排课单元：{}",
                        teachingClassUnit.getTeachingClassName() + "--" + teachingClassUnit.getCourseName());

                //查找符合条件的教室课时分组信息列表，分配给教学班排课单元
                teachingClassUnit = specifiedTimelotRoom.startAllocation(teachingClassUnit, roomList, classHourMap);

                //获取分配的map信息
                LinkedList<TimeslotRoom> timeslotList = teachingClassUnit.getTimeslotRoomList();
                //如果教室课时分组信息，分配成功
                if (timeslotList != null
                        && !timeslotList.isEmpty()) {
                    //如果是可删除的教室课时分组，需要从当前列表中删除
                    roomList  = removeTimeslotRoomList(roomList, timeslotList);

                    break;
                }
            }
        }

        return  teachingClassUnit;
    }
}
